using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Markup;
using Rhizome.Contract;
using Rhizome.Contract.Diag;
using Rhizome.Contract.Visitor;
using Rhizome.Internal;
using Rhizome.Model.Validation;
using Rhizome.Model.Visitor;

namespace Rhizome.Model
{
	[ContentProperty("States")]
	public abstract class AutomatonBase<TState, TPlant> : RhizomeElement, IEventSink, IValidate,
	                                                      IStateInitialize, IMessageSink, IStatesDiag, IAutomaton
		where TState : class, IIdentityAccessor<object>
	{
		private readonly RhizomeElementKeyedCollection<Edge> _edges;
		private readonly Dictionary<TState, object> _history = new Dictionary<TState, object>();
		private readonly object _stateLock = new object();
		private readonly RhizomeElementKeyedCollection<TState> _states;
		private bool _isInTransition;
		private TState _state;

		protected AutomatonBase()
		{
			_states = new RhizomeElementKeyedCollection<TState>(this);
			_edges = new RhizomeElementKeyedCollection<Edge>(this);
		}

		public KeyedCollection<object, TState> States
		{
			[DebuggerStepThrough]
			get { return _states; }
		}

		public KeyedCollection<object, Edge> Edges
		{
			[DebuggerStepThrough]
			get { return _edges; }
		}

		/// <summary>
		/// 	Initial state ID
		/// </summary>
		public object InitialState { get; set; }

		public TState State
		{
			[DebuggerStepThrough]
			get { return _state; }
			private set
			{
				if (_state == value) return;
				if (Logger != null) Logger.WriteLine("{0}.set_State({1})", this, value);
				_state = value;
			}
		}

		public TPlant Plant
		{
			[DebuggerStepThrough]
			get { return (TPlant) GetValue(PlantProperty); }
			[DebuggerStepThrough]
			set { SetValue(PlantProperty, value); }
		}

		#region Overrides of RhizomeElement

		protected override IEnumerable<IEnumerable> GetElementCollections()
		{
			return base.GetElementCollections().Concat(new IEnumerable[] {Edges, States});
		}

		#endregion

		#region IEventSink Members

		public virtual void CastEvent(object source, object @event)
		{
			if (source == null) throw new ArgumentNullException("source");
			if (@event == null) throw new ArgumentNullException("event");

			if (Monitor.TryEnter(_stateLock) == false)
			{
				if (Logger != null)
					Logger.WriteLine(
						"(sic!) {0}.CastEvent(Source = {1}, Event = {2}) is locked by another thread",
						this, source, @event);
				return;
			}

			try
			{
				if (Logger != null) Logger.WriteLine("{0}.CastEvent(Source = {1}, Event = {2})", this, source, @event);

				if (Equals(source, State) == false)
				{
					if (Logger != null) Logger.WriteLine("(sic!) {0}.CastEvent. State != Source ({1})", this, source);
					return;
				}

				if (_isInTransition)
				{
					throw new InvalidOperationException(
						string.Format(
							"{0}.CastEvent(Source = {1}, Event = {2}) fired in transition state. Use IForwardEvent instead",
							this, source, @event));
				}


				_isInTransition = true;
				_history.Clear();

				var stateInitialize = State as IStateInitialize;
				if (stateInitialize != null) stateInitialize.Cleanup();

				InternalCastEvent(State, @event);
			}
			finally
			{
				_isInTransition = false;
				Monitor.Exit(_stateLock);
			}
		}

		#endregion

		#region IMessageSink Members

		bool IMessageSink.Send(object source, IMessage message, ICollection<IMessageSink> stack)
		{
			if (Logger != null)
				Logger.WriteLine("A {0}.Send({1}, {2})", GetType().Name, source, message);

			stack.Add(this);

			var m = State as IMessageSink;
			if (m != null && stack.Contains(m) == false)
			{
				return m.Send(this, message, stack);
			}

			if (Parent == null) return false;

			var p = Parent as IMessageSink;
			return p == null ? false : p.Send(this, message, stack);
		}

		#endregion

		#region Implementation of IStateInitialize

		void IStateInitialize.Initialize()
		{
			OnStart();
		}

		void IStateInitialize.Cleanup()
		{
			OnStop();
		}

		#endregion

		#region IValidate Members

		public virtual IEnumerable<IValidationResult> GetValidationResults()
		{
			var stateIds = StateIds.ToArray();

			if (InitialState == null) yield return ValidationResult.InitialStateNotSet;
			else if (stateIds.Contains(InitialState) == false) yield return ValidationResult.StateNotFound(InitialState);

			foreach (var state in States)
			{
				var eventsQuery =
					from eventsDiag in state.GetAllElements<IEventsDiag>(false)
					from e in eventsDiag.Events
					select e;
					
				var stateEvents = eventsQuery.ToArray();

				if (stateEvents.Any() == false)
					yield return ValidationResult.Warning("State {0} has no events".With(state));
				
				else if (Edges.Contains(state.Id) == false)
					yield return ValidationResult.Error("State {0} has events but no edge defined".With(state));

				else
				{
					var edge = Edges[state.Id];
					var edgeEvents = edge.Events.ToArray();
					
					foreach (var e in stateEvents.Except(edgeEvents))
						yield return ValidationResult.Error("State {0} has event {1} but no such transition defined".With(state, e));

					foreach (var e in edgeEvents.Except(stateEvents))
						yield return ValidationResult.Warning("State {0} has no event {1} (but such transition defined)".With(state, e));
				}
			}

			foreach (var edge in Edges)
			{
				var transitionStates =
					from statesDiag in edge.GetAllElements<IStatesDiag>()
					from s in statesDiag.StateIds
					select s;

				foreach (var s in transitionStates.Except(stateIds))
					yield return ValidationResult.Error("State {0} defines transition to State {1} but {1} do not exists".With(edge.State, s));
			}

			if (InitialState == null || stateIds.Contains(InitialState) == false) 
				yield break;
			
			var usedStates = new List<object>();
			CollectStates(InitialState, usedStates);

			foreach (var s in stateIds.Except(usedStates))
				yield return ValidationResult.Warning("State {0} not used".With(s));
		}

		private void CollectStates(object stateId, ICollection<object> usedStates)
		{
			if (States.Contains(stateId) == false) return;
			usedStates.Add(stateId);
			
			if (Edges.Contains(stateId) == false) return;
			
			var eventsQuery =
				from eventsDiag in States[stateId].GetAllElements<IEventsDiag>(false)
				from e in eventsDiag.Events
				select e;
			var stateEvents = eventsQuery.ToArray();

			var targetStateIds =
				from t in Edges[stateId].Transitions
				from statesDiag in t.GetAllElements<IStatesDiag>()
				from s in statesDiag.StateIds
				from e in stateEvents
				where Equals(t.Event, e)
				select s;

			foreach (var s in targetStateIds)
			{
				if (usedStates.Contains(s)) continue;
				CollectStates(s, usedStates);
			}
		}

		#endregion

		private void InternalCastEvent(TState fromState, object @event)
		{
			var stateId = fromState.Id;
			if (Edges.Contains(stateId) == false)
				throw new EdgeNotFoundException<TState>(State, @event);

			var edge = Edges[stateId];
			if (edge.Transitions.Contains(@event) == false)
				throw new EdgeNotFoundException<TState>(State, @event);

			var transition = edge.Transitions[@event];
			var nextStateId = transition.GetTransitionState();
			if (States.Contains(nextStateId) == false)
				throw new StateNotFoundException(stateId);

			// exiting state
			var stateActions = fromState as IStateActions;
			if (stateActions != null) stateActions.ExitAction();

			// edge transition action
			var actionSource = transition as IActionSource;
			var transitionAction = actionSource == null ? null : actionSource.Action;
			if (transitionAction != null) transitionAction.Process();

			ChangeState(fromState, nextStateId);
		}

		private void ChangeState(TState fromState, object nextStateId)
		{
			if (nextStateId == null)
			{
				OnStop();
				return;
			}

			var nextState = States[nextStateId];

			var stateActions = nextState as IStateActions;
			if (stateActions != null) stateActions.EntryAction();

			var forwardEvent = nextState as IForwardEvent;
			if (forwardEvent != null)
			{
				var e = forwardEvent.GetForwardEvent();
				if (e != null)
				{
					if (Logger != null)
						Logger.WriteLine(
							"{0}[{1}].InternalCastEvent(NextState = {2}, ForwardEvent = {3})",
							GetType().Name, fromState, nextState, e);

					if (fromState != null)
					{
						if (_history.ContainsKey(fromState))
						{
							throw new InvalidOperationException(
								"Event Sink infinite loop detected: " +
								FormatHistory());
						}

						_history.Add(fromState, e);
					}

					// todo: remove recursion?
					InternalCastEvent(nextState, e);
					return;
				}
			}

			// set new state
			State = nextState;

			var init = nextState as IStateInitialize;
			if (init != null) init.Initialize();
		}

		private string FormatHistory()
		{
			return (from p in _history select "{0} + {1}".With(p.Key, p.Value)).ToArray().Join(" -> ");
		}

		public override T GetValue<T>(RhizomeProperty<T> property)
		{
			if (ReferenceEquals(property, EventSyncProperty))
				return (T) ((object) this);

			return base.GetValue(property);
		}

		public void Start()
		{
			if (State != null) throw new InvalidOperationException("State must be null");
			OnStart();
		}

		public void Stop()
		{
			if (State == null) return;
			OnStop();
		}

		protected virtual void OnStart()
		{
			ChangeState(null, InitialState);
		}

		protected virtual void OnStop()
		{
			var stateInitialize = State as IStateInitialize;
			if (stateInitialize != null) stateInitialize.Cleanup();
			State = null;
		}

		public override string ToString()
		{
			var s = "A {0}".With(GetType().Name);
			if (State != null) s += "/S {0}".With(State.Id);
			return s; 
		}

		#region Implementation of IStatesDiag

		IEnumerable<object> IStatesDiag.StateIds
		{
			[DebuggerStepThrough]
			get { return StateIds; }
		}

		#endregion

		private IEnumerable<object> StateIds
		{
			[DebuggerStepThrough]
			get { return from s in States select s.Id; }
		}

	}
}